Descripcion base de datos
Titulo de la base de datos:
Forest Covertype data, Remote Sensing and GIS Program Department of Forest Sciences College of Natural Resources Colorado State University.
El dataset consiste en medidas cartograficas de areas silvestres en el Bosque Nacional Roosevelt en el norte de Colorado en USA y el tipo de covertura vegetal presente. Se realizan en celdas de 30 X 30 metros. Estos bosques tienen poca intervencion humana, por ende las areas caracterizan no usos (como agricultura, poblacion etc) sino clase de bosque predominante. En total son 12 medidas cartograficas y 7 grandes tipos de covertura vegetal.
El dataset:
Numero de columnas:
length(df)
[1] 55
Numero de observaciones:
nrow(df)
[1] 581011
Atributos:
names(df[,c(1:10,55)])
[1] "Elevation" "Aspect"
[3] "Slope" "Horizontal_Distance_To_Hydrology"
[5] "Vertical_Distance_To_Hydrology" "Horizontal_Distance_To_Roadways"
[7] "Hillshade_9am" "Hillshade_Noon"
[9] "Hillshade_3pm" "Horizontal_Distance_To_Fire_Points"
[11] "Forest_Cover_Type"
Otros atributos son cuatro areas silvestres y 40 tipos de suelo.
El tipo de atributo, las unidades y su descripcion:
| Elevation |
quantitative |
meters |
Elevation in meters |
| Aspect |
quantitative |
azimuth |
Aspect in degrees azimuth |
| Slope |
quantitative |
degrees |
Slope in degrees |
| Horizontal_Distance_To_Hydrology |
quantitative |
meters |
Horz Dist to nearest surface water features |
| Vertical_Distance_To_Hydrology |
quantitative |
meters |
Vert Dist to nearest surface water features |
| Horizontal_Distance_To_Roadways |
quantitative |
meters |
Horz Dist to nearest roadway |
| Hillshade_9am |
quantitative |
0 to 255 index |
Hillshade index at 9am, summer solstice |
| Hillshade_Noon |
quantitative |
0 to 255 index |
Hillshade index at noon, summer soltice |
| Hillshade_3pm |
quantitative |
0 to 255 index |
Hillshade index at 3pm, summer solstice |
| Horizontal_Distance_To_Fire_Points |
quantitative |
meters |
Horz Dist to nearest wildfire ignition points |
| Wilderness_Area (4 binary columns) |
qualitative |
0 (absence) or 1 (presence) |
Wilderness area designation |
| Soil_Type (40 binary columns) |
qualitative |
0 (absence) or 1 (presence |
Soil Type designation |
| Cover_Type (7 types) |
integer |
1 to 7 |
Forest Cover Type designation |
Las cuatro areas silvestres:
- Rawah Wilderness Area
- Neota Wilderness Area
- Comanche Peak Wilderness Area
- Cache la Poudre Wilderness Area
Tipo de covertura forestal:
- Spruce/Fir (picea -pino-/abeto)
- Lodgepole Pine (pino)
- Ponderosa Pine (pino)
- Cottonwood/Willow (sauces)
- Aspen (alamos)
- Douglas-fir (pino oregon)
- Krummholz (vegetación atrofiada y deformada, arbol “bandera”)
Missings
No hay missings.
colSums(is.na(df))
Elevation Aspect
0 0
Slope Horizontal_Distance_To_Hydrology
0 0
Vertical_Distance_To_Hydrology Horizontal_Distance_To_Roadways
0 0
Hillshade_9am Hillshade_Noon
0 0
Hillshade_3pm Horizontal_Distance_To_Fire_Points
0 0
Wilderness_Area_1 Wilderness_Area_2
0 0
Wilderness_Area_3 Wilderness_Area_4
0 0
Soil_Type1 Soil_Type2
0 0
Soil_Type3 Soil_Type4
0 0
Soil_Type5 Soil_Type6
0 0
Soil_Type7 Soil_Type8
0 0
Soil_Type9 Soil_Type10
0 0
Soil_Type11 Soil_Type12
0 0
Soil_Type13 Soil_Type14
0 0
Soil_Type15 Soil_Type16
0 0
Soil_Type17 Soil_Type18
0 0
Soil_Type19 Soil_Type20
0 0
Soil_Type21 Soil_Type22
0 0
Soil_Type23 Soil_Type24
0 0
Soil_Type25 Soil_Type26
0 0
Soil_Type27 Soil_Type28
0 0
Soil_Type29 Soil_Type30
0 0
Soil_Type31 Soil_Type32
0 0
Soil_Type33 Soil_Type34
0 0
Soil_Type35 Soil_Type36
0 0
Soil_Type37 Soil_Type38
0 0
Soil_Type39 Soil_Type40
0 0
Forest_Cover_Type
0
###Variables categoricas###
Una de las variables consiste en medidas del tipo de suelo presente. En el dataset esta variable se mapeo a 40 one-hot variables. Cada variable corresponde a un tipo de suelo, catalogado con un codigo que incluye informacion de la zona climatica y zona geologica. La distribucion de esta variable es la siguiente :
barplot(sort(table(df_suelos$suelo)),las=2, log="y", xlab = "suelo", col = "#1b98e0")
png('barsuelo.png')
barplot(sort(table(df_suelos$suelo)),las=2, log="y", xlab = "suelo", col = "#1b98e0")
dev.off()
png
2

El suelo mas abundante tiene el doble de frecuencia que el siguiente mas abundante y su frecuencia esta 4 ordenes de magnitud por encima del suelo menos frecuente. En vista de que son muchas variables individuales a ser consideradas y no pueden ser combinadas (no sin conocimiento experto en geologia) para reducir su numero. No sera considerada en el analisis.
Otra de las variables categoricas es la zona silvestre de la celda. La abundancia de zonas:
barplot(sort(table(df_areas$areas)),las=2, xlab = "areas silvestres", col = "#F4A582")
png('barareas.png')
barplot(sort(table(df_areas$areas)),las=2, xlab = "areas silvestres", col = "#F4A582")
dev.off()
png
2

Las areas silvestres 3 y 4 son alrededor de 8 veces mas abundantes en el dataset que las 1 y 2. Tambien se podria usar para clasificar pero se escoge la clase de cobertura forestal.
Descripcion de la variable objetivo, las clases de cobertura vegetal:
library(plotly)
labels = c('Spruce/Fir','Lodgepole Pine','Ponderosa Pine','Cottonwood/Willow','Aspen','Douglas-fir',
'Krummholz')
values <- aggregate(df$Elevation~ df$Forest_Cover_Type, data=df, FUN=length)
fig <- plot_ly(type='pie', labels=labels, values=values$`df$Elevation`,
textinfo='label+percent',
insidetextorientation='radial',showlegend = FALSE)
fig
#png('covertype.png')
#fig
#dev.off()
#pie(table(df$Forest_Cover_Type),main='Tipos de covertura')
#barplot(table(df$World.region), xlab = "Region", ylab = "Cantidad de Ciudades", main="Ciudades por region",cex.names = .3, las=2)
Se puede observar que los datos estan bastante desbalanceados. Esto incidiría en una regresión pero no en un LDA.
Resumen de los datos:
df_max<-apply(df[,1:10],2,max)
df_min<-apply(df[,1:10],2,min)
df_n <- sweep(df[,1:10], 2, df_min, "-")
range <- df_max-df_min
df_n <- sweep(df_n, 2, range, "/")
boxplot(df_n,las=2)

Todas las distribuciones son sesgadas y tienen muchos outliers. Para clasificar, si se va a aplicar un LDA, hay que garantizar normalidad y que las medias sean distintas, pero se vera mas adelante. Tambien es importante garantizar que los atributos sean independientes.
La variable Aspect es bimodal y probablemente Slope es multimodal. Todas las variables tienen colas largas, excepto Hillshade_3pm. En todas las variables excepto en Elevation las clases siguen la misma tendencia. En esta variable las distribuciones para cada grupo estan centradas distinto:
ggplot2.multiplot(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9,plot10, cols=4)
png('histograms.png')
ggplot2.multiplot(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9,plot10, cols=3)
dev.off()
png
2

Correlaciones
library(ggcorrplot)
corr <- round(cor(subset0[,1:10]), 1)
p.mat<- cor_pmat(subset0[,1:10])
ggcorrplot(corr, lab = TRUE,
outline.col = "white",lab_size = 3, tl.cex=5,method = "circle",hc.order = TRUE, p.mat = p.mat)
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
png('correlation.png')
ggcorrplot(corr, lab = TRUE,
outline.col = "white",lab_size = 3, tl.cex=5, method = "circle",hc.order = TRUE, p.mat = p.mat)
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
dev.off()
png
2

Las variables hillshade estan relacionadas con aspect, slope y entre ellas. Esto tiene que ver con como se calculan las cantidades hillshade que son los sombreados que se dibujan sobre los mapas cartograficos. Se supone una iluminacion simulada que depende de la orientacion a la fuente de luz, la cual esta basada en las variables aspect y slope. Las cantidades distancia vertical y horizontal a cursos de agua tambien estan relacionadas y se calculan con slope y elevation. La distancia horizontal a caminos y a puntos de fuego tambien se calculan con la elevacion y estan relacionados entre si.
Con base en esto acoto las variables a Elevation, Aspect, Slope, Hillshade_noon, Horizontal_Distance_Roadways y Horizontal_Distance_to_Hydrology.
library(GGally)
library(data.table)
color <- as.factor(subset[,7])
ggpairs(subset[,1:5], aes(color = color, alpha = 0.5),lower = list(combo = "count"),upper = "blank",)

Se pueden observar tambien las distribuciones de las clases, son sesgadas como se vio en los boxplots. La variable que podria servir mas para distinguir clases podria ser Elevation.
subsubset <- subset[,c(1:4,6,8,11)]
Analisis explotario con PCA
library(ggbiplot)
library(plyr)
datos_pca <- prcomp(subset[,1:5,7],scale=TRUE)
p <- ggbiplot(datos_pca, obs.scale=0.01,alpha = 0.3,groups=subset$cover_type)
p <- p + xlim(-3, 2) + ylim(-2, 3)
plot(p)
png('pca.png')
plot(p)
dev.off()
png
2

LDA
Habiendo determinado que la variable de clase es discriminante, calculamos el lda:
library(MASS)
modelo_lda <- lda(formula = cover_type ~ Elevation + Aspect + Slope + Horizontal_Distance_To_Hydrology + Horizontal_Distance_To_Roadways + Hillshade_Noon, data = subset)
Ahora se prueba el modelo:
predicciones <- predict(object = modelo_lda, newdata = testeo, method = "predictive")
table(clase, predicciones$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 510 203 0 0 0 0 7
2 199 791 11 0 0 10 0
3 0 24 77 1 0 6 0
4 0 0 10 1 0 0 0
5 1 32 0 0 0 0 0
6 0 20 45 0 0 8 0
7 62 2 0 0 0 0 5
Los errores de prediccion:
training_error <- mean(clase != predicciones$class) * 100
training_error
[1] 31.25926
El error da bastante grande…
Con una variable mas:
library(klaR)
testeo$clase <- as.factor(testeo$clase)
partimat(clase ~ ., data = testeo, method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7))


Sin esa variable:
library(klaR)
testeo$clase <- as.factor(testeo$clase)
partimat(clase ~ ., data = testeo[,c(1:5,7)], method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7),scaled=TRUE)
png('partiplot.png')
partimat(clase ~ ., data = testeo[,c(1:5,7)], method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7),scaled=TRUE)
dev.off()
png
2

Discrimina muy mal. Ahora, si transformo las variables con un log:
library(IDPmisc)
df_trans <- log(subset[,1:6])
subset_trans <- cbind(df_trans,cover_type)
subset_t <- NaRV.omit(subsubset_trans)
subset_t$cover_type <- as.factor(subset_t$cover_type)
library(MASS)
modelo_lda_trans <- lda(formula = cover_type ~. , data = subset_t)
library(IDPmisc)
library(dplyr)
test_trans <- log(test)
test_t <- cbind(test_trans,clase)
test_t <- NaRV.omit(test_t)
clase_t <-test_t$clase
predicciones_t <- predict(object = modelo_lda_trans, newdata = test_t)
table(clase_t, predicciones_t$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 0 682 0 0 0 0 0
2 0 969 0 0 0 0 0
3 0 103 0 0 0 0 0
4 0 8 0 0 0 0 0
5 0 29 0 0 0 0 0
6 0 68 0 0 0 0 0
7 0 67 0 0 0 0 0
training_error_t <- mean(clase_t != predicciones_t$class) * 100
training_error_t
[1] 49.68847
Pesimo.
Si considero una variable mas, sin transformar:
library(MASS)
modelo_lda_2 <- lda(formula = cover_type ~. , data = subset_2)
predicciones_2 <- predict(object = modelo_lda_2, newdata = testeo_2)
table(testeo_2$clase, predicciones_2$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 516 200 0 0 0 0 4
2 201 790 11 0 0 9 0
3 0 25 69 4 0 10 0
4 0 0 10 1 0 0 0
5 0 33 0 0 0 0 0
6 0 19 44 0 0 10 0
7 63 1 0 0 0 0 5
training_error_2 <- mean(testeo_2$clase != predicciones_2$class) * 100
training_error_2
[1] 31.30864
Considerar las variables con logaritmo o considerar una variable adicional no mejoro la prediccion. Se intentara ahora con un lda robusto:
library(MASS)
modelo_qda <- qda(formula = cover_type ~ ., data = subset)
predicciones_qda <- predict(object = modelo_qda, newdata = testeo, method = "predictive")
table(clase, predicciones_qda$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 66 15 0 4 161 37 437
2 92 66 1 30 572 86 164
3 0 0 3 68 8 29 0
4 0 0 0 11 0 0 0
5 2 0 0 0 29 2 0
6 0 0 4 37 7 25 0
7 0 0 0 0 1 0 68
training_error_qda <- mean(clase != predicciones_qda$class) * 100
training_error_qda
[1] 86.76543
Da peor…
Con las clases escaladas:
datos_escalados <- as.data.frame(scale(subset[,1:5]))
subset_esc <- cbind(datos_escalados,cover_type)
subset_esc$cover_type <- as.factor(subset_esc$cover_type)
test de Shapiro para los datos escalados:
subset_esc$cover_type <- as.factor(subset_esc$cover_type)
library(mvnormtest)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='1',1:5]))
shapitest <- test$p.value
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='2',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='3',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='4',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='5',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='6',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='7',1:5]))
shapitest <- append(shapitest,test$p.value)
shapitest
[1] 5.239585e-15 2.098729e-18 1.165629e-01 5.931935e-04 1.401489e-09 1.025656e-02 1.953662e-10
library(MASS)
modelo_lda_esc <- lda(formula = cover_type ~., data = subset_esc)
test_esc <- as.data.frame(scale(test))
test_esc <- cbind(test_esc,clase)
test_esc$clase <- as.factor(test_esc$clase)
predicciones_esc <- predict(object = modelo_lda_esc, newdata = test_esc, method = "predictive")
table(clase, predicciones_esc$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 521 192 0 0 0 0 7
2 216 774 11 0 0 10 0
3 0 25 76 1 0 6 0
4 0 0 10 1 0 0 0
5 1 32 0 0 0 0 0
6 0 21 43 0 0 9 0
7 63 1 0 0 0 0 5
training_error_esc <- mean(clase != predicciones_esc$class) * 100
training_error_esc
[1] 31.55556
El mejor resultado fue el inicial. En general se predicen muy mal las clases menos abundantes.
El qda con los datos escalados:
library(MASS)
modelo_qda_esc <- qda(formula = cover_type ~ ., data = subset[,1:5,7])
predicciones_qda_esc <- predict(object = modelo_qda_esc, newdata = testeo[,1:5,7], method = "predictive")
table(clase, predicciones_qda_esc$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 115 21 0 2 168 10 404
2 129 99 0 15 552 74 142
3 0 0 6 63 8 31 0
4 0 0 0 10 0 1 0
5 2 0 0 0 29 2 0
6 0 0 0 39 7 27 0
7 0 0 0 0 1 0 68
training_errorqda_esc <- mean(clase != predicciones_qda_esc$class) * 100
training_errorqda_esc
[1] 82.51852
Se realizara el analisis con solo dos clases:
subsubset2 <-subset[(subset$cover_type==2),]
subsubset1 <-subset[(subset$cover_type==1),]
subsubset <-rbind(subsubset1,subsubset2)
subsubset$cover_type <- droplevels(subsubset$cover_type)
El test de normalidad multivariada:
library(mvnormtest)
testing <- mshapiro.test(t(subsubset[subsubset$cover_type=='1',1:6]))
shapitest <- testing$p.value
testing <- mshapiro.test(t(subsubset[subsubset$cover_type=='2',1:6]))
shapitest <- append(shapitest,testing$p.value)
shapitest
[1] 2.929115e-30 3.038726e-36
Rechaza normalidad para cada variable para cada nivel de la variable.
Con las variables estandarizadas:
library(clusterSim)
#subsubset_es <- scale(subsubset[,1:6], center = median(subsubset))
subsubset_es <- data.Normalization (subsubset[,1:6],type="n2",normalization="column")
subsubset_es <- cbind(subsubset_es,subsubset[,7])
Con las variables estandarizadas y sacando hillshade:
library(mvnormtest)
subsubset_2 <- subsubset_es[,c(1:5,7)]
testing <- mshapiro.test(t(subsubset_2[subsubset_2$cover_type=='1',1:5]))
shapitest <- testing$p.value
testing <- mshapiro.test(t(subsubset_2[subsubset_2$cover_type=='2',1:5]))
shapitest <- append(shapitest,testing$p.value)
shapitest
[1] 5.239585e-15 2.098729e-18
Rechaza, sin sacar y sacando hillshade.
Testeando homocedasticidad:
library(biotools)
boxM(data=subsubset[,1:6],grouping=subsubset[,7])
Rechaza igualdad de varianzas.
Como el test M de Box es sensible a la falta de normalidad, realizamos el de Levene:
library(car)
Loading required package: carData
Attaching package: ‘car’
The following object is masked from ‘package:dplyr’:
recode
leveneTest( Elevation + Aspect + Slope + Horizontal_Distance_To_Hydrology + Horizontal_Distance_To_Roadways + Hillshade_Noon ~ subsubset$cover_type, data = subsubset)
Levene's Test for Homogeneity of Variance (center = median)
Df F value Pr(>F)
group 1 24.874 6.365e-07 ***
4268
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
El valor es menor que el nivel de significancia 0.001. Rechazamos la hipotesis nula y concluimos que las varianzas no son iguales.
De todas maneras hacemos el test de Hotelling para testear diferencia de medias:
library(Hotelling)
fitProd = hotelling.test(.~ subsubset$cover_type, data = subsubset)
fitProd
Rechaza igualdad de medias, pero como no se cumple la normalidad multivariada este resultado no es confiable.
LDA con dos clases
el conjunto de test para dos clases:
library(dplyr)
testeo2 <-testeo[(testeo$clase==2),]
testeo1 <-testeo[(testeo$clase==1),]
testeo_2class <-rbind(testeo1,testeo2)
testeo_2class$clase <- droplevels(testeo_2class$clase)
clase_2class <- testeo_2class[,7]
test_2class <-testeo_2class[,1:6]
library(MASS)
modelo_lda_2class <- lda(formula = cover_type ~. , data = subsubset)
la clasificacion ingenua:
predicciones_2class_0 <- predict(object = modelo_lda_2class, newdata = subsubset)
table(subsubset$cover_type, predicciones_2class_0$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 1343 488
2 525 1914
training_error_2cl_0 <- mean(subsubset$cover_type != predicciones_2class_0$class) * 100
training_error_2cl_0
[1] 23.72365
predicciones_2class <- predict(object = modelo_lda_2class, newdata = testeo_2class)
table(clase_2class, predicciones_2class$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 516 204
2 199 812
training_error_2cl <- mean(clase_2class != predicciones_2class$class) * 100
training_error_2cl
[1] 23.28134
library(klaR)
partimat(subsubset$cover_type ~ ., data = subsubset, method = "lda", col.correct=NA, col.wrong=NA, plot.matrix=FALSE,image.colors =rainbow(2))


La variable que mejor separa es elevacion.
Con las variables estandarizadas:
library(MASS)
modelo_lda_2class_est <- lda(formula = cover_type ~. , data = subsubset_es)
la clasificacion ingenua:
predicciones_2class_es_0 <- predict(object = modelo_lda_2class_est, newdata = subsubset_es)
table(subsubset_es$cover_type, predicciones_2class_es_0$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 1343 488
2 525 1914
training_error_2cl_es_0 <- mean(subsubset_es$cover_type != predicciones_2class_es_0$class) * 100
training_error_2cl_es_0
[1] 23.72365
library(clusterSim)
#subsubset_es <- scale(subsubset[,1:6], center = median(subsubset))
testeo_es <- data.Normalization (testeo_2class[,1:6],type="n2",normalization="column")
testeo_es <- cbind(testeo_es,testeo_2class$clase)
testeo_es <- rename(testeo_es, cover_type=`testeo_2class$clase`)
predicciones_2class_es <- predict(object = modelo_lda_2class_est, newdata = testeo_es)
table(testeo_es$clase, predicciones_2class_es$class, dnn = c("Clase real", "Clase predicha"))
training_error_2cl_es <- mean(testeo_es$clase != predicciones_2class_es$class) * 100
training_error_2cl_es
[1] 23.62796
Haciendo el discriminante robusto:
library(MASS)
modelo_qda_2class_est <- qda(formula = cover_type ~. , data = subsubset_es)
prediccionesqda_2c_es_0 <- predict(object = modelo_qda_2class_est, newdata = subsubset_es)
table(subsubset_es$cover_type, prediccionesqda_2c_es_0$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 1397 434
2 613 1826
training_errorqda_2cl_es_0 <- mean(subsubset_es$cover_type != prediccionesqda_2c_es_0$class) * 100
training_errorqda_2cl_es_0
[1] 24.51991
prediccionesqda_2c_es <- predict(object = modelo_qda_2class_est, newdata = testeo_es)
table(testeo_es$clase, prediccionesqda_2c_es$class, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 559 161
2 252 759
training_errorqda_2cl_es <- mean(testeo_es$clase != prediccionesqda_2c_es$class) * 100
training_errorqda_2cl_es
[1] 23.85904
Incluso empeora un poco.
Support Vector machine
Clasificando con svm el dataset con las 7 clases:
library(e1071)
model.svm = svm( subset$cover_type ~ ., data = subset[,c(1:5,7)], kernel = "radial", cost = 10, scale = TRUE)
print(model.svm)
Call:
svm(formula = subset$cover_type ~ ., data = subset[, c(1:5, 7)], kernel = "radial", cost = 10,
scale = TRUE)
Parameters:
SVM-Type: C-classification
SVM-Kernel: radial
cost: 10
Number of Support Vectors: 3264
predicciones_svm = predict(model.svm, subset[,c(1:5,7)])
table(subset$cover_type, predicciones_svm, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 1342 465 0 0 0 0 24
2 373 2035 24 0 0 4 3
3 0 60 225 0 0 9 0
4 0 0 7 13 0 1 0
5 0 60 0 0 18 0 0
6 0 66 46 0 0 35 0
7 86 4 0 0 0 0 100
training_error_svm_0 <- mean(subset$cover_type != predicciones_svm) * 100
training_error_svm_0
[1] 24.64
El kernel que mejor da es el radial.
predicciones_svm_te = predict(model.svm, testeo)
table(testeo$clase, predicciones_svm_te, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2 3 4 5 6 7
1 499 209 0 0 0 0 12
2 169 821 13 0 3 4 1
3 0 26 76 1 0 5 0
4 0 0 8 3 0 0 0
5 0 29 0 0 4 0 0
6 0 27 38 0 0 8 0
7 37 3 0 0 0 0 29
training_error_svm <- mean(testeo$clase != predicciones_svm_te) * 100
training_error_svm
[1] 28.88889
Si lo hacemos con menos variables:
subsubset1 <-subset[(subset$cover_type==1),]
subsubset2 <-subset[(subset$cover_type==2),]
subsubset <-rbind(subsubset1,subsubset2)
subsubset$cover_type <- droplevels(subsubset$cover_type)
library(e1071)
model.svm_2 = svm( subsubset$cover_type ~ ., data = subsubset[,c(1:5,7)], kernel = "radial", cost = 10, scale = TRUE)
print(model.svm)
Call:
svm(formula = subset$cover_type ~ ., data = subset[, c(1:5, 7)], kernel = "radial", cost = 10,
scale = TRUE)
Parameters:
SVM-Type: C-classification
SVM-Kernel: radial
cost: 10
Number of Support Vectors: 3264
predicciones_svm_2 = predict(model.svm_2, subsubset[,c(1:5,7)])
table(subsubset$cover_type, predicciones_svm_2, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 1359 472
2 374 2065
training_error_svm_2 <- mean(subsubset$cover_type != predicciones_svm_2) * 100
training_error_svm_2
[1] 19.81265
Ahora con el test:
predicciones_svm_te_2 = predict(model.svm_2, testeo_2class)
table(testeo_2class$clase, predicciones_svm_te_2, dnn = c("Clase real", "Clase predicha"))
Clase predicha
Clase real 1 2
1 501 219
2 170 841
training_error_svm_2_te <- mean(testeo_2class$clase != predicciones_svm_te_2) * 100
training_error_svm_2_te
[1] 22.47256
Graficando para todas las clases:
plot(model.svm, data=subset[,c(1:5,7)],Elevation ~ Aspect, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Slope, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Slope, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Horizontal_Distance_To_Hydrology ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)],Elevation ~ Aspect, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Slope, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Slope, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Horizontal_Distance_To_Hydrology ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

Se dibujan mas fronteras cuando la variable Elevation esta presente.
Clasificacion no supervisada
Para la clasificacion no supervisada, comenzamos con metodos jerarquicos porque k-means es sensible a outliers:
datos_clust <- subset
mat_dist <- dist(x = subset, method = "euclidean")
Dendrogramas para distintos metodos, suponemos 7 clusters:
hc_complete <- hclust(d = mat_dist, method = "complete")
hc_average <- hclust(d = mat_dist, method = "average")
hc_single <- hclust(d = mat_dist, method = "single")
hc_ward <- hclust(d = mat_dist, method = "ward.D2")
hc_cn <- hclust(d = mat_dist, method = "centroid")
Construyendo los dendrogramas:
cantidad_clusters = 7
plot(hc_complete)
rect.hclust(hc_complete, k=cantidad_clusters, border="red") #

jer_complete<-cutree(hc_complete,k=cantidad_clusters) #
datos_clust$jer_complete=jer_complete
cantidad_clusters = 7
plot(hc_average)
rect.hclust(hc_average, k=cantidad_clusters, border="red") #

jer_average<-cutree(hc_average,k=cantidad_clusters) #
datos_clust$jer_average=jer_average
cantidad_clusters = 7
plot(hc_single)
rect.hclust(hc_single, k=cantidad_clusters, border="red") #

jer_single<-cutree(hc_single,k=cantidad_clusters) #
#datos$jer_complete=jer_complete
Da feisimo…
cantidad_clusters = 7
plot(hc_ward)
rect.hclust(hc_ward, k=cantidad_clusters, border="red") #

jer_ward<-cutree(hc_ward,k=cantidad_clusters) #
datos_clust$jer_ward=jer_ward
cantidad_clusters = 7
plot(hc_cn)
rect.hclust(hc_cn, k=cantidad_clusters, border="red") #

jer_cn<-cutree(hc_cn,k=cantidad_clusters) #
datos_clust$jer_cn=jer_cn
Los coeficientes de correlacion cofenetica:
cor(x = mat_dist, cophenetic(hc_complete))
[1] 0.7768551
cor(x = mat_dist, cophenetic(hc_average))
[1] 0.7736942
cor(x = mat_dist, cophenetic(hc_single))
[1] 0.1769797
cor(x = mat_dist, cophenetic(hc_ward))
[1] 0.6320018
cor(x = mat_dist, cophenetic(hc_cn))
[1] 0.7774966
El linkage single es el peor.
datos_clust$jer_complete <- as.factor(datos_clust$jer_cn)
p1 <- ggplot(datos_clust, aes(x=Elevation, y=Aspect, color=jer_cn)) +
geom_point() + scale_color_brewer(palette="Dark2")
p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=jer_cn)) +
geom_point()
p2 + scale_color_brewer(palette="Dark2")
p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
geom_point()
p3 + scale_color_brewer(palette="Dark2")
p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
geom_point()
p4 + scale_color_brewer(palette="Dark2")
library(ggplot2)
library(easyGgplot2)
datos_clust$jer_complete <- as.factor(datos_clust$jer_cn)
p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=jer_cn, alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")
p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=jer_cn,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none") +labs(y='H_Hydrology')
p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p7 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p9 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p10 <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(x='H_Hydrology',y='H_Roadways')
ggplot2.multiplot(p2,p3,p4,p7,p9,p10,cols=3)
png('jerarquical.png')
ggplot2.multiplot(p2,p3,p4,p7,p9,p10,cols=3)
dev.off()
png
2

library(ggplot2)
p <- ggplot(datos_clust, aes(x=Aspect, y=Slope, color=jer_cn)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
p <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
p <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
p <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
library(ggplot2)
p <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
p <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=jer_complete)) +
geom_point()
p + scale_color_brewer(palette="Dark2")
Solo lo hice con el primer metodo de cluster jerarquico que fue el que mejor dio el coeficiente cofrenetico.
library(cluster)
datos_kmeans = datos_clust[1:5]
cantidad_clusters=7
CL = kmeans(scale(datos_kmeans),cantidad_clusters)
datos_kmeans$kmeans = CL$cluster
library(ggplot2)
library(easyGgplot2)
datos_kmeans$kmeans <- as.factor(datos_kmeans$kmeans)
p1 <- ggplot(datos_clust, aes(x=Elevation, y=Aspect, color=datos_kmeans$kmeans, alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")
p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=datos_kmeans$kmeans, alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")
p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none") +labs(y='H_Hydrology')
p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p5 <- ggplot(datos_clust, aes(x=Aspect, y=Slope, color=datos_kmeans$kmeans, alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")
p6 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans, alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none") +labs(y='H_Hydrology')
p7 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p8 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p9 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')
p10 <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(x='H_Hydrology',y='H_Roadways')
ggplot2.multiplot(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,cols=3)
png('jerarquical_2.png')
ggplot2.multiplot(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,cols=3)
dev.off()
png
2

LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gQUlEIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89RkFMU0UpCmBgYAoKIyBEZXNjcmlwY2lvbiBiYXNlIGRlIGRhdG9zCgojIyMgVGl0dWxvIGRlIGxhIGJhc2UgZGUgZGF0b3M6CgpGb3Jlc3QgQ292ZXJ0eXBlIGRhdGEsIFJlbW90ZSBTZW5zaW5nIGFuZCBHSVMgUHJvZ3JhbSBEZXBhcnRtZW50IG9mIEZvcmVzdCBTY2llbmNlcyBDb2xsZWdlIG9mIE5hdHVyYWwgUmVzb3VyY2VzIENvbG9yYWRvIFN0YXRlIFVuaXZlcnNpdHkuCgpFbCBkYXRhc2V0IGNvbnNpc3RlIGVuIG1lZGlkYXMgY2FydG9ncmFmaWNhcyBkZSBhcmVhcyBzaWx2ZXN0cmVzIGVuIGVsIEJvc3F1ZSBOYWNpb25hbCBSb29zZXZlbHQgZW4gZWwgCm5vcnRlIGRlIENvbG9yYWRvIGVuIFVTQSB5IGVsIHRpcG8gZGUgY292ZXJ0dXJhIHZlZ2V0YWwgcHJlc2VudGUuIFNlIHJlYWxpemFuIGVuIGNlbGRhcyAgZGUgMzAgWCAzMCBtZXRyb3MuIApFc3RvcyBib3NxdWVzIHRpZW5lbiBwb2NhIGludGVydmVuY2lvbiAKaHVtYW5hLCBwb3IgZW5kZSBsYXMgYXJlYXMgY2FyYWN0ZXJpemFuIG5vIHVzb3MgKGNvbW8gYWdyaWN1bHR1cmEsIHBvYmxhY2lvbiBldGMpIHNpbm8gY2xhc2UgZGUgYm9zcXVlIApwcmVkb21pbmFudGUuIEVuIHRvdGFsIHNvbiAxMiBtZWRpZGFzIGNhcnRvZ3JhZmljYXMgeSA3IGdyYW5kZXMgdGlwb3MgZGUgY292ZXJ0dXJhIHZlZ2V0YWwuCgpFbCBkYXRhc2V0OgoJCk51bWVybyBkZSBjb2x1bW5hczoKYGBge3J9Cmxlbmd0aChkZikKYGBgCgpOdW1lcm8gZGUgb2JzZXJ2YWNpb25lczogIApgYGB7cn0KCm5yb3coZGYpCmBgYAojIyMgQXRyaWJ1dG9zOgoKYGBge3J9Cm5hbWVzKGRmWyxjKDE6MTAsNTUpXSkKYGBgCk90cm9zIGF0cmlidXRvcyBzb24gY3VhdHJvIGFyZWFzIHNpbHZlc3RyZXMgeSA0MCB0aXBvcyBkZSBzdWVsby4KCkVsIHRpcG8gZGUgYXRyaWJ1dG8sIGxhcyB1bmlkYWRlcyB5IHN1IGRlc2NyaXBjaW9uOgoKTmFtZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICBEYXRhIFR5cGUgICAgICAgfCAgICAgIE1lYXN1cmVtZW50ICAgICAgICB8ICAgICAgICAgICAgRGVzY3JpcHRpb24KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpFbGV2YXRpb24gICAgICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEVsZXZhdGlvbiBpbiBtZXRlcnMKQXNwZWN0ICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICBxdWFudGl0YXRpdmUgICAgfCAgYXppbXV0aCAgICAgICAgICAgICAgICB8ICBBc3BlY3QgaW4gZGVncmVlcyBhemltdXRoClNsb3BlICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgcXVhbnRpdGF0aXZlICAgIHwgIGRlZ3JlZXMgICAgICAgICAgICAgICAgfCAgU2xvcGUgaW4gZGVncmVlcwpIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEhvcnogRGlzdCB0byBuZWFyZXN0IHN1cmZhY2Ugd2F0ZXIgZmVhdHVyZXMKVmVydGljYWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5ICAgIHwgICBxdWFudGl0YXRpdmUgICAgfCAgbWV0ZXJzICAgICAgICAgICAgICAgICB8ICBWZXJ0IERpc3QgdG8gbmVhcmVzdCBzdXJmYWNlIHdhdGVyIGZlYXR1cmVzCkhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMgICB8ICAgcXVhbnRpdGF0aXZlICAgIHwgIG1ldGVycyAgICAgICAgICAgICAgICAgfCAgSG9yeiBEaXN0IHRvIG5lYXJlc3Qgcm9hZHdheQpIaWxsc2hhZGVfOWFtICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCA5YW0sIHN1bW1lciBzb2xzdGljZQpIaWxsc2hhZGVfTm9vbiAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCBub29uLCBzdW1tZXIgc29sdGljZQpIaWxsc2hhZGVfM3BtICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCAzcG0sIHN1bW1lciBzb2xzdGljZQpIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0ZpcmVfUG9pbnRzfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEhvcnogRGlzdCB0byBuZWFyZXN0IHdpbGRmaXJlIGlnbml0aW9uIHBvaW50cwpXaWxkZXJuZXNzX0FyZWEgKDQgYmluYXJ5IGNvbHVtbnMpfCAgIHF1YWxpdGF0aXZlICAgICB8ICAwIChhYnNlbmNlKSBvciAxIChwcmVzZW5jZSl8ICBXaWxkZXJuZXNzIGFyZWEgZGVzaWduYXRpb24KU29pbF9UeXBlICg0MCBiaW5hcnkgY29sdW1ucykgICAgIHwgICBxdWFsaXRhdGl2ZSAgICAgfCAgMCAoYWJzZW5jZSkgb3IgMSAocHJlc2VuY2UgfCAgU29pbCBUeXBlIGRlc2lnbmF0aW9uCkNvdmVyX1R5cGUgKDcgdHlwZXMpICAgICAgICAgICAgICB8ICAgaW50ZWdlciAgICAgICAgIHwgIDEgdG8gNyAgICAgICAgICAgICAgICAgICAgIHwgIEZvcmVzdCBDb3ZlciBUeXBlIGRlc2lnbmF0aW9uCgpMYXMgY3VhdHJvIGFyZWFzIHNpbHZlc3RyZXM6ICAJCgoqIFJhd2FoIFdpbGRlcm5lc3MgQXJlYQoqIE5lb3RhIFdpbGRlcm5lc3MgQXJlYQoqIENvbWFuY2hlIFBlYWsgV2lsZGVybmVzcyBBcmVhCiogQ2FjaGUgbGEgUG91ZHJlIFdpbGRlcm5lc3MgQXJlYQoKClRpcG8gZGUgY292ZXJ0dXJhIGZvcmVzdGFsOgkKCiogU3BydWNlL0ZpciAocGljZWEgLXBpbm8tL2FiZXRvKQoqIExvZGdlcG9sZSBQaW5lIChwaW5vKQoqIFBvbmRlcm9zYSBQaW5lIChwaW5vKQoqIENvdHRvbndvb2QvV2lsbG93IChzYXVjZXMpCiogQXNwZW4gKGFsYW1vcykKKiBEb3VnbGFzLWZpciAocGlubyBvcmVnb24pCiogS3J1bW1ob2x6ICh2ZWdldGFjacOzbiBhdHJvZmlhZGEgeSBkZWZvcm1hZGEsIGFyYm9sICJiYW5kZXJhIikKCiMjIyBNaXNzaW5ncwoKTm8gaGF5IG1pc3NpbmdzLgoKYGBge3J9CmNvbFN1bXMoaXMubmEoZGYpKQpgYGAKIyMjVmFyaWFibGVzIGNhdGVnb3JpY2FzIyMjCgpVbmEgZGUgbGFzIHZhcmlhYmxlcyBjb25zaXN0ZSBlbiBtZWRpZGFzIGRlbCB0aXBvIGRlIHN1ZWxvIHByZXNlbnRlLiBFbiBlbCBkYXRhc2V0IGVzdGEgdmFyaWFibGUgc2UgbWFwZW8gYSA0MCBvbmUtaG90IHZhcmlhYmxlcy4gQ2FkYSB2YXJpYWJsZSBjb3JyZXNwb25kZSBhIHVuIHRpcG8gZGUgc3VlbG8sIGNhdGFsb2dhZG8gY29uIHVuIGNvZGlnbyBxdWUgaW5jbHV5ZSBpbmZvcm1hY2lvbiBkZSBsYSB6b25hIGNsaW1hdGljYSB5IHpvbmEgZ2VvbG9naWNhLiBMYSBkaXN0cmlidWNpb24gZGUgZXN0YSB2YXJpYWJsZSBlcyBsYSBzaWd1aWVudGUgOgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCgpkZl9zdWVsb3MgPC0gZGZbLDE1OjU0XQoKZm9yIChqIGluIDE6bGVuZ3RoKGRmX3N1ZWxvcykpewogICAgZm9yIChpIGluIDE6bnJvdyhkZl9zdWVsb3MpKXsKICAgICAgICBpZiAoZGZfc3VlbG9zW2ksal09PTEpewogICAgICAgICAgICBkZl9zdWVsb3NbaSw0MV08LXBhc3RlKCJTb2lsX1R5cGUiLGosc2VwID0gIiIpCiAgICAgICAgfQogICAgfQp9CmRmX3N1ZWxvcyA8LSByZW5hbWUoZGZfc3VlbG9zLCBzdWVsbz1WNDEpCgpiYXJwbG90KHNvcnQodGFibGUoZGZfc3VlbG9zJHN1ZWxvKSksbGFzPTIsIGxvZz0ieSIsIHhsYWIgPSAic3VlbG8iLCBjb2wgPSAiIzFiOThlMCIpCnBuZygnYmFyc3VlbG8ucG5nJykKYmFycGxvdChzb3J0KHRhYmxlKGRmX3N1ZWxvcyRzdWVsbykpLGxhcz0yLCBsb2c9InkiLCB4bGFiID0gInN1ZWxvIiwgY29sID0gIiMxYjk4ZTAiKQoKZGV2Lm9mZigpCgpgYGAKCkVsIHN1ZWxvIG1hcyBhYnVuZGFudGUgdGllbmUgZWwgZG9ibGUgZGUgZnJlY3VlbmNpYSBxdWUgZWwgc2lndWllbnRlIG1hcyBhYnVuZGFudGUgeSBzdSBmcmVjdWVuY2lhIGVzdGEgNCBvcmRlbmVzIGRlIG1hZ25pdHVkIHBvciBlbmNpbWEgZGVsIHN1ZWxvIG1lbm9zIGZyZWN1ZW50ZS4gRW4gdmlzdGEgZGUgcXVlIHNvbiBtdWNoYXMgdmFyaWFibGVzIGluZGl2aWR1YWxlcyBhIHNlciBjb25zaWRlcmFkYXMgeSBubyBwdWVkZW4gc2VyIGNvbWJpbmFkYXMgKG5vIHNpbiBjb25vY2ltaWVudG8gZXhwZXJ0byBlbiBnZW9sb2dpYSkgcGFyYSByZWR1Y2lyIHN1IG51bWVyby4gTm8gc2VyYSBjb25zaWRlcmFkYSBlbiBlbCBhbmFsaXNpcy4KCk90cmEgZGUgbGFzIHZhcmlhYmxlcyBjYXRlZ29yaWNhcyBlcyBsYSB6b25hIHNpbHZlc3RyZSBkZSBsYSBjZWxkYS4gTGEgYWJ1bmRhbmNpYSBkZSB6b25hczoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKZGZfYXJlYXMgPC0gZGZbLDExOjE0XQoKZm9yIChqIGluIDE6bGVuZ3RoKGRmX2FyZWFzKSl7CiAgICBmb3IgKGkgaW4gMTpucm93KGRmX2FyZWFzKSl7CiAgICAgICAgaWYgKGRmX2FyZWFzW2ksal09PTEpewogICAgICAgICAgICBkZl9hcmVhc1tpLDVdPC1wYXN0ZSgiQXJlYSIsaixzZXAgPSAiIikKICAgICAgICB9CiAgICB9Cn0KZGZfYXJlYXM8LSByZW5hbWUoZGZfYXJlYXMsIGFyZWFzPVY1KQoKYmFycGxvdChzb3J0KHRhYmxlKGRmX2FyZWFzJGFyZWFzKSksbGFzPTIsIHhsYWIgPSAiYXJlYXMgc2lsdmVzdHJlcyIsIGNvbCA9ICIjRjRBNTgyIikKCnBuZygnYmFyYXJlYXMucG5nJykKCmJhcnBsb3Qoc29ydCh0YWJsZShkZl9hcmVhcyRhcmVhcykpLGxhcz0yLCB4bGFiID0gImFyZWFzIHNpbHZlc3RyZXMiLCBjb2wgPSAiI0Y0QTU4MiIpCgpkZXYub2ZmKCkKYGBgCkxhcyBhcmVhcyBzaWx2ZXN0cmVzIDMgeSA0IHNvbiBhbHJlZGVkb3IgZGUgOCB2ZWNlcyBtYXMgYWJ1bmRhbnRlcyBlbiBlbCBkYXRhc2V0IHF1ZSBsYXMgMSB5IDIuIFRhbWJpZW4gc2UgcG9kcmlhIHVzYXIgcGFyYSBjbGFzaWZpY2FyIHBlcm8gc2UgZXNjb2dlIGxhIGNsYXNlIGRlIGNvYmVydHVyYSBmb3Jlc3RhbC4KCkRlc2NyaXBjaW9uIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvLCBsYXMgY2xhc2VzIGRlIGNvYmVydHVyYSB2ZWdldGFsOgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShwbG90bHkpCgpsYWJlbHMgPSBjKCdTcHJ1Y2UvRmlyJywnTG9kZ2Vwb2xlIFBpbmUnLCdQb25kZXJvc2EgUGluZScsJ0NvdHRvbndvb2QvV2lsbG93JywnQXNwZW4nLCdEb3VnbGFzLWZpcicsCiAgICAgICAgICAnS3J1bW1ob2x6JykKdmFsdWVzIDwtIGFnZ3JlZ2F0ZShkZiRFbGV2YXRpb25+IGRmJEZvcmVzdF9Db3Zlcl9UeXBlLCBkYXRhPWRmLCBGVU49bGVuZ3RoKQpmaWcgPC0gcGxvdF9seSh0eXBlPSdwaWUnLCBsYWJlbHM9bGFiZWxzLCB2YWx1ZXM9dmFsdWVzJGBkZiRFbGV2YXRpb25gLCAKICAgICAgICAgICAgICAgdGV4dGluZm89J2xhYmVsK3BlcmNlbnQnLAogICAgICAgICAgICAgICBpbnNpZGV0ZXh0b3JpZW50YXRpb249J3JhZGlhbCcsc2hvd2xlZ2VuZCA9IEZBTFNFKQpmaWcKCiNwbmcoJ2NvdmVydHlwZS5wbmcnKQoKI2ZpZwoKI2Rldi5vZmYoKQoKI3BpZSh0YWJsZShkZiRGb3Jlc3RfQ292ZXJfVHlwZSksbWFpbj0nVGlwb3MgZGUgY292ZXJ0dXJhJykKI2JhcnBsb3QodGFibGUoZGYkV29ybGQucmVnaW9uKSwgeGxhYiA9ICJSZWdpb24iLCB5bGFiID0gIkNhbnRpZGFkIGRlIENpdWRhZGVzIiwgICAgICAgICBtYWluPSJDaXVkYWRlcyBwb3IgcmVnaW9uIixjZXgubmFtZXMgPSAuMywgbGFzPTIpIApgYGAKClNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBsb3MgZGF0b3MgZXN0YW4gYmFzdGFudGUgZGVzYmFsYW5jZWFkb3MuIEVzdG8gaW5jaWRpcsOtYSBlbiB1bmEgcmVncmVzacOzbiBwZXJvIG5vIGVuIHVuIExEQS4KClJlc3VtZW4gZGUgbG9zIGRhdG9zOgoKYGBge3J9CmRmX21heDwtYXBwbHkoZGZbLDE6MTBdLDIsbWF4KQpkZl9taW48LWFwcGx5KGRmWywxOjEwXSwyLG1pbikKZGZfbiA8LSBzd2VlcChkZlssMToxMF0sIDIsIGRmX21pbiwgIi0iKQpyYW5nZSA8LSBkZl9tYXgtZGZfbWluCmRmX24gPC0gc3dlZXAoZGZfbiwgMiwgcmFuZ2UsICIvIikKYm94cGxvdChkZl9uLGxhcz0yKQpgYGAKClRvZGFzIGxhcyBkaXN0cmlidWNpb25lcyBzb24gc2VzZ2FkYXMgeSB0aWVuZW4gbXVjaG9zIG91dGxpZXJzLiBQYXJhIGNsYXNpZmljYXIsIHNpIHNlIHZhIGEgYXBsaWNhciB1biBMREEsIGhheSBxdWUgZ2FyYW50aXphciBub3JtYWxpZGFkIHkgcXVlIGxhcyBtZWRpYXMgc2VhbiBkaXN0aW50YXMsIHBlcm8gc2UgdmVyYSBtYXMgYWRlbGFudGUuIFRhbWJpZW4gZXMgaW1wb3J0YW50ZSBnYXJhbnRpemFyIHF1ZSBsb3MgYXRyaWJ1dG9zIHNlYW4gaW5kZXBlbmRpZW50ZXMuCgpMYSB2YXJpYWJsZSBBc3BlY3QgZXMgYmltb2RhbCB5IHByb2JhYmxlbWVudGUgU2xvcGUgZXMgbXVsdGltb2RhbC4gVG9kYXMgbGFzIHZhcmlhYmxlcyB0aWVuZW4gY29sYXMgbGFyZ2FzLCBleGNlcHRvIEhpbGxzaGFkZV8zcG0uIEVuIHRvZGFzIGxhcyB2YXJpYWJsZXMgZXhjZXB0byBlbiBFbGV2YXRpb24gbGFzIGNsYXNlcyBzaWd1ZW4gbGEgbWlzbWEgdGVuZGVuY2lhLiBFbiBlc3RhIHZhcmlhYmxlIGxhcyBkaXN0cmlidWNpb25lcyBwYXJhIGNhZGEgZ3J1cG8gZXN0YW4gY2VudHJhZGFzIGRpc3RpbnRvOgoKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShlYXN5R2dwbG90MikKCgpzZXQuc2VlZCg0MikKCnJvd3MgPC0gc2FtcGxlKG5yb3coZGYpKQpkZl9yYW5kIDwtIGRmW3Jvd3MsIF0KZGZfbmV3IDwtIGRmX3JhbmRbc2VxKDEsNTAwMDAwLDEwMCksYygxOjEwKV0KY292ZXJfdHlwZSA8LSBkZl9yYW5kW3NlcSgxLDUwMDAwMCwxMDApLDU1XQpzdWJzZXQwIDwtIGNiaW5kKGRmX25ldyxjb3Zlcl90eXBlKQpzdWJzZXQwJGNvdmVyX3R5cGUgPC0gYXMuZmFjdG9yKHN1YnNldDAkY292ZXJfdHlwZSkKCiMgSGlzdG9ncmFtYSBFbGV2YXRpb24KcGxvdDEgPC0gZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nRWxldmF0aW9uJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJywgbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgRWxldmF0aW9uIiwgeHRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHl0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB4VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHlUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeHRpdGxlID0gIkVsZXZhdGlvbiIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIEFzcGVjdApwbG90MiA8LSBnZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdBc3BlY3QnLGdyb3VwTmFtZT0nY292ZXJfdHlwZScsIG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEFzcGVjdCIsIHh0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB5dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeFRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB5VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHh0aXRsZSA9ICJBc3BlY3QiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBTbG9wZQpwbG90MyA8LSBnZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdTbG9wZScsIGdyb3VwTmFtZT0nY292ZXJfdHlwZScsbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgU2xvcGUiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiU2xvcGUiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIGh5ZHJvCnBsb3Q0IDwtZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3knLCBncm91cE5hbWU9J2NvdmVyX3R5cGUnLG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEhvcml6LiBkaXN0YW5jZSB0byBoeWRyb2xvZ3kiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9oeWRyb2xvZ3kiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSB2IGh5ZHJvCnBsb3Q1IDwtZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nVmVydGljYWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5JywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBWZXJ0LiBkaXN0YW5jZSB0byBoeWRyb2xvZ3kiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiVl9oeWRyb2xvZ3kiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIHJvYWQKcGxvdDYgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBIb3Jpei4gZGlzdGFuY2UgdG8gcm9hZHdheXMiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9yb2Fkd2F5cyIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIGhpbGwgOQpwbG90NyA8LWdncGxvdDIuaGlzdG9ncmFtKGRhdGE9c3Vic2V0MCwgeE5hbWU9J0hpbGxzaGFkZV85YW0nLCBncm91cE5hbWU9J2NvdmVyX3R5cGUnLG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEhpbGxzaGFkZV85YW0iLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSGlsbHNoYWRlXzlhbSIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIGhpbGwgMTIKcGxvdDggPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIaWxsc2hhZGVfTm9vbicsIGdyb3VwTmFtZT0nY292ZXJfdHlwZScsbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgSGlsbHNoYWRlX25vb24iLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSGlsbHNoYWRlX25vb24iLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoaWxsIDMKcGxvdDkgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIaWxsc2hhZGVfM3BtJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBIaWxsc2hhZGVfM3BtIiwgeHRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHl0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB4VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHlUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeHRpdGxlID0gIkhpbGxzaGFkZV8zcG0iLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIGZpcmVwb2ludApwbG90MTAgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0ZpcmVfUG9pbnRzJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJywgbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgSG9yaXouIGRpc3RhbmNlIHRvIGZpcmVwb2ludHMiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9maXJlcG9pbnRzIiwgeXRpdGxlID0gIkZyZWN1ZW5jaWEiLCBiYWNrZ3JvdW5kQ29sb3I9IndoaXRlIiwgYnJld2VyUGFsZXR0ZT0iUGFpcmVkIixhbHBoYT0wLjUsc2hvd0xlZ2VuZD1GQUxTRSxwb3NpdGlvbj0ic3RhY2siKQojCgpnZ3Bsb3QyLm11bHRpcGxvdChwbG90MSxwbG90MixwbG90MyxwbG90NCxwbG90NSxwbG90NixwbG90NyxwbG90OCxwbG90OSxwbG90MTAsIGNvbHM9NCkKCnBuZygnaGlzdG9ncmFtcy5wbmcnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocGxvdDEscGxvdDIscGxvdDMscGxvdDQscGxvdDUscGxvdDYscGxvdDcscGxvdDgscGxvdDkscGxvdDEwLCBjb2xzPTMpCgpkZXYub2ZmKCkKYGBgCgojIyMgQ29ycmVsYWNpb25lcwoKCmBgYHtyfQpsaWJyYXJ5KGdnY29ycnBsb3QpCgpjb3JyIDwtIHJvdW5kKGNvcihzdWJzZXQwWywxOjEwXSksIDEpCnAubWF0PC0gY29yX3BtYXQoc3Vic2V0MFssMToxMF0pCgpnZ2NvcnJwbG90KGNvcnIsIGxhYiA9IFRSVUUsCiAgICAgb3V0bGluZS5jb2wgPSAid2hpdGUiLGxhYl9zaXplID0gMywgdGwuY2V4PTUsbWV0aG9kID0gImNpcmNsZSIsaGMub3JkZXIgPSBUUlVFLCBwLm1hdCA9IHAubWF0KQoKcG5nKCdjb3JyZWxhdGlvbi5wbmcnKQoKZ2djb3JycGxvdChjb3JyLCBsYWIgPSBUUlVFLAogICAgIG91dGxpbmUuY29sID0gIndoaXRlIixsYWJfc2l6ZSA9IDMsIHRsLmNleD01LCBtZXRob2QgPSAiY2lyY2xlIixoYy5vcmRlciA9IFRSVUUsIHAubWF0ID0gcC5tYXQpCgpkZXYub2ZmKCkKCmBgYAoKTGFzIHZhcmlhYmxlcyBoaWxsc2hhZGUgZXN0YW4gcmVsYWNpb25hZGFzIGNvbiBhc3BlY3QsIHNsb3BlIHkgZW50cmUgZWxsYXMuIEVzdG8gdGllbmUgcXVlIHZlciBjb24gY29tbyBzZSBjYWxjdWxhbiBsYXMgY2FudGlkYWRlcyBoaWxsc2hhZGUgcXVlIHNvbiBsb3Mgc29tYnJlYWRvcyBxdWUgc2UgZGlidWphbiBzb2JyZSBsb3MgbWFwYXMgY2FydG9ncmFmaWNvcy4gU2Ugc3Vwb25lIHVuYSBpbHVtaW5hY2lvbiBzaW11bGFkYSBxdWUgZGVwZW5kZSBkZSBsYSBvcmllbnRhY2lvbiBhIGxhIGZ1ZW50ZSBkZSBsdXosIGxhIGN1YWwgZXN0YSBiYXNhZGEgZW4gbGFzIHZhcmlhYmxlcyBhc3BlY3QgeSBzbG9wZS4gTGFzIGNhbnRpZGFkZXMgZGlzdGFuY2lhIHZlcnRpY2FsIHkgaG9yaXpvbnRhbCBhIGN1cnNvcyBkZSBhZ3VhIHRhbWJpZW4gZXN0YW4gcmVsYWNpb25hZGFzIHkgc2UgY2FsY3VsYW4gY29uIHNsb3BlIHkgZWxldmF0aW9uLiBMYSBkaXN0YW5jaWEgaG9yaXpvbnRhbCBhIGNhbWlub3MgeSBhIHB1bnRvcyBkZSBmdWVnbyB0YW1iaWVuIHNlIGNhbGN1bGFuIGNvbiBsYSBlbGV2YWNpb24geSBlc3RhbiByZWxhY2lvbmFkb3MgZW50cmUgc2kuCgpDb24gYmFzZSBlbiBlc3RvIGFjb3RvIGxhcyB2YXJpYWJsZXMgYSBFbGV2YXRpb24sIEFzcGVjdCwgU2xvcGUsIEhpbGxzaGFkZV9ub29uLCBIb3Jpem9udGFsX0Rpc3RhbmNlX1JvYWR3YXlzIHkgSG9yaXpvbnRhbF9EaXN0YW5jZV90b19IeWRyb2xvZ3kuCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9CmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGRhdGEudGFibGUpCgpjb2xvciA8LSBhcy5mYWN0b3Ioc3Vic2V0Wyw3XSkKZ2dwYWlycyhzdWJzZXRbLDE6NV0sIGFlcyhjb2xvciA9IGNvbG9yLCBhbHBoYSA9IDAuNSksbG93ZXIgPSBsaXN0KGNvbWJvID0gImNvdW50IiksdXBwZXIgPSAiYmxhbmsiLCkKYGBgCgpTZSBwdWVkZW4gb2JzZXJ2YXIgdGFtYmllbiBsYXMgZGlzdHJpYnVjaW9uZXMgZGUgbGFzIGNsYXNlcywgc29uIHNlc2dhZGFzIGNvbW8gc2UgdmlvIGVuIGxvcyBib3hwbG90cy4gTGEgdmFyaWFibGUgcXVlIHBvZHJpYSBzZXJ2aXIgbWFzIHBhcmEgZGlzdGluZ3VpciBjbGFzZXMgcG9kcmlhIHNlciBFbGV2YXRpb24uCgoKYGBge3J9CnN1YnN1YnNldCA8LSBzdWJzZXRbLGMoMTo0LDYsOCwxMSldCmBgYAoKCiMjIyBBbmFsaXNpcyBleHBsb3RhcmlvIGNvbiBQQ0EKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShnZ2JpcGxvdCkKbGlicmFyeShwbHlyKQoKZGF0b3NfcGNhIDwtIHByY29tcChzdWJzZXRbLDE6NSw3XSxzY2FsZT1UUlVFKQpwIDwtIGdnYmlwbG90KGRhdG9zX3BjYSwgb2JzLnNjYWxlPTAuMDEsYWxwaGEgPSAwLjMsZ3JvdXBzPXN1YnNldCRjb3Zlcl90eXBlKQpwIDwtIHAgKyB4bGltKC0zLCAyKSArIHlsaW0oLTIsIDMpCgpwbG90KHApCgpwbmcoJ3BjYS5wbmcnKQoKcGxvdChwKQoKZGV2Lm9mZigpCgpgYGAKCgojIENsYXNpZmljYWNpb24gU3VwZXJ2aXNhZGEKCiMjIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMKCkVsIHByaW1lciBtZXRvZG8gYSBpbXBsZW1lbnRhciBlcyBMREEuIFBhcmEgcXVlIHRlbmdhIHZhbGlkZXogZWwgbWV0b2RvIGRlYmVuIGN1bXBsaXJzZSBsb3Mgc3VwdWVzdG9zIGRlIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhIHBhcmEgY2FkYSBuaXZlbCBkZSBjYWRhIHZhcmlhYmxlIHkgZGUgaG9tb2NlZGFzdGljaWRhZC4KClBhcmEgZWwgdGVzdCBkZSBub3JtYWxpZGFkIG11bHRpdmFyaWFkYSB1c2Ftb3MgdGVzdCBkZSBTaGFwaXJvOgoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMScsMTo2XSkpCnNoYXBpdGVzdCA8LSB0ZXN0JHAudmFsdWUKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMicsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMycsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNCcsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNScsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNicsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNycsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKCnNoYXBpdGVzdApgYGAKUmVjaGF6YSBub3JtYWxpZGFkIHBhcmEgdG9kb3MgbG9zIG5pdmVsZXMgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcy4gUGFyYSBlbCB0b3RhbDoKCmBgYHtyfQpsaWJyYXJ5KG12bm9ybXRlc3QpCm1zaGFwaXJvLnRlc3QodChzdWJzZXRbLDE6Nl0pKQpgYGAKClNpIGVsIG5pdmVsIGRlIHNpZ25pZmljYWNpb24gcG9yIGRlZmVjdG8gZXMgZGUgMC4wNSwgcmVjaGF6YSBsYSBub3JtYWxpZGFkIG11bHRpdmFyaWFkYS4gCgpFbCBzaWd1aWVudGUgc3VwdWVzdG8gZGUgaG9tb2NlZGFzdGljaWRhZCBsbyBldmFsdWFtb3MgY29uIGVsIHRlc3QgTSBkZSBCb3g6CgpgYGB7cn0KbGlicmFyeShiaW90b29scykKCmJveE0oZGF0YT1zdWJzdWJzZXRbLDE6Nl0sZ3JvdXBpbmc9c3Vic3Vic2V0Wyw3XSkKYGBgClJlY2hhemEgbGEgaG9tb2dlbmVpZGFkIGRlIHZhcmlhbnphcy4gU2UgdGVuZHJpYSBxdWUgcmVhbGl6YXIgdW4gTERBIHJvYnVzdG8gKGN1YWRyYXRpY28pLgoKSWd1YWwsIGEgcGVzYXIgZGUgaGFiZXIgcmVjaGF6YWRvIGxhIG5vcm1hbGlkYWQgaGFjZW1vcyBlbCB0ZXN0IGRlIEhvdGVsbGluZyBwYXJhIGRldGVybWluYXIgc2kgbGFzIG1lZGlhcyBkZSBsb3MgZ3J1cG9zIHNvbiBkaXN0aW50YXM6CgpgYGB7cn0KbGlicmFyeShIb3RlbGxpbmcpCgpmaXRQcm9kID0gaG90ZWxsaW5nLnRlc3QoLn4gY292ZXJfdHlwZSwgZGF0YSA9IHN1YnN1YnNldCkgCmZpdFByb2QKYGBgClBvciBlbmRlIHJlY2hhemFtb3MgcXVlIGxhcyBtZWRpYXMgc2VhbiBpZ3VhbGVzLiBFbCBjb3Zlcl90eXBlIGVzIGRpc2NyaW1pbmFudGUuCgojIExEQQoKSGFiaWVuZG8gZGV0ZXJtaW5hZG8gcXVlIGxhIHZhcmlhYmxlIGRlIGNsYXNlIGVzIGRpc2NyaW1pbmFudGUsIGNhbGN1bGFtb3MgZWwgbGRhOgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhIDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+IEVsZXZhdGlvbiArIEFzcGVjdCArIFNsb3BlICsgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3kgKyBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzICsgSGlsbHNoYWRlX05vb24sIGRhdGEgPSBzdWJzZXQpCgpgYGAKCkFob3JhIHNlIHBydWViYSBlbCBtb2RlbG86CgpgYGB7cn0KcHJlZGljY2lvbmVzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGEsIG5ld2RhdGEgPSB0ZXN0ZW8sIG1ldGhvZCA9ICJwcmVkaWN0aXZlIikKdGFibGUoY2xhc2UsIHByZWRpY2Npb25lcyRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKTG9zIGVycm9yZXMgZGUgcHJlZGljY2lvbjoKCmBgYHtyfQp0cmFpbmluZ19lcnJvciA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lcyRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yIApgYGAKRWwgZXJyb3IgZGEgYmFzdGFudGUgZ3JhbmRlLi4uCgpDb24gdW5hIHZhcmlhYmxlIG1hczoKCmBgYHtyfQpsaWJyYXJ5KGtsYVIpIAoKdGVzdGVvJGNsYXNlIDwtIGFzLmZhY3Rvcih0ZXN0ZW8kY2xhc2UpCgpwYXJ0aW1hdChjbGFzZSB+IC4sIGRhdGEgPSB0ZXN0ZW8sIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLHBsb3QubWF0cml4PUZBTFNFLGltYWdlLmNvbG9ycyA9cmFpbmJvdyg3KSwpCmBgYApTaW4gZXNhIHZhcmlhYmxlOgoKYGBge3J9CmxpYnJhcnkoa2xhUikgCgp0ZXN0ZW8kY2xhc2UgPC0gYXMuZmFjdG9yKHRlc3RlbyRjbGFzZSkKCnBhcnRpbWF0KGNsYXNlIH4gLiwgZGF0YSA9IHRlc3Rlb1ssYygxOjUsNyldLCBtZXRob2QgPSAibGRhIiwgY29sLmNvcnJlY3Q9TkEsIGNvbC53cm9uZz1OQSxwbG90Lm1hdHJpeD1GQUxTRSxpbWFnZS5jb2xvcnMgPXJhaW5ib3coNyksc2NhbGVkPVRSVUUpCgpwbmcoJ3BhcnRpcGxvdC5wbmcnKQoKcGFydGltYXQoY2xhc2UgfiAuLCBkYXRhID0gdGVzdGVvWyxjKDE6NSw3KV0sIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLHBsb3QubWF0cml4PUZBTFNFLGltYWdlLmNvbG9ycyA9cmFpbmJvdyg3KSxzY2FsZWQ9VFJVRSkKCmRldi5vZmYoKQoKYGBgCgpEaXNjcmltaW5hIG11eSBtYWwuIEFob3JhLCBzaSB0cmFuc2Zvcm1vIGxhcyB2YXJpYWJsZXMgY29uIHVuIGxvZzoKCmBgYHtyfQpsaWJyYXJ5KElEUG1pc2MpCgpkZl90cmFucyA8LSBsb2coc3Vic2V0WywxOjZdKQpzdWJzZXRfdHJhbnMgPC0gY2JpbmQoZGZfdHJhbnMsY292ZXJfdHlwZSkKc3Vic2V0X3QgPC0gTmFSVi5vbWl0KHN1YnN1YnNldF90cmFucykKc3Vic2V0X3QkY292ZXJfdHlwZSA8LSBhcy5mYWN0b3Ioc3Vic2V0X3QkY292ZXJfdHlwZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkoTUFTUykKCm1vZGVsb19sZGFfdHJhbnMgPC0gbGRhKGZvcm11bGEgPSBjb3Zlcl90eXBlIH4uICwgZGF0YSA9IHN1YnNldF90KQoKYGBgCgpgYGB7cn0KbGlicmFyeShJRFBtaXNjKQpsaWJyYXJ5KGRwbHlyKQoKdGVzdF90cmFucyA8LSBsb2codGVzdCkKdGVzdF90IDwtIGNiaW5kKHRlc3RfdHJhbnMsY2xhc2UpCnRlc3RfdCA8LSBOYVJWLm9taXQodGVzdF90KQpjbGFzZV90IDwtdGVzdF90JGNsYXNlCmBgYAoKCmBgYHtyfQpwcmVkaWNjaW9uZXNfdCA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fbGRhX3RyYW5zLCBuZXdkYXRhID0gdGVzdF90KQp0YWJsZShjbGFzZV90LCBwcmVkaWNjaW9uZXNfdCRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcl90IDwtIG1lYW4oY2xhc2VfdCAhPSBwcmVkaWNjaW9uZXNfdCRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3QgCmBgYApQZXNpbW8uCgpTaSBjb25zaWRlcm8gdW5hIHZhcmlhYmxlIG1hcywgc2luIHRyYW5zZm9ybWFyOgoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX2xkYV8yIDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+LiAsIGRhdGEgPSBzdWJzZXRfMikKCmBgYApgYGB7cn0KcHJlZGljY2lvbmVzXzIgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX2xkYV8yLCBuZXdkYXRhID0gdGVzdGVvXzIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfMiRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcl8yIDwtIG1lYW4odGVzdGVvXzIkY2xhc2UgIT0gcHJlZGljY2lvbmVzXzIkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl8yIApgYGAKQ29uc2lkZXJhciBsYXMgdmFyaWFibGVzIGNvbiBsb2dhcml0bW8gbyBjb25zaWRlcmFyIHVuYSB2YXJpYWJsZSBhZGljaW9uYWwgbm8gbWVqb3JvIGxhIHByZWRpY2Npb24uIFNlIGludGVudGFyYSBhaG9yYSBjb24gdW4gbGRhIHJvYnVzdG86CgoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX3FkYSA8LSBxZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic2V0KQoKYGBgCgpgYGB7cn0KcHJlZGljY2lvbmVzX3FkYSA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fcWRhLCBuZXdkYXRhID0gdGVzdGVvLCBtZXRob2QgPSAicHJlZGljdGl2ZSIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfcWRhJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYApgYGB7cn0KdHJhaW5pbmdfZXJyb3JfcWRhIDwtIG1lYW4oY2xhc2UgIT0gcHJlZGljY2lvbmVzX3FkYSRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3FkYQpgYGAKRGEgcGVvci4uLgoKQ29uIGxhcyBjbGFzZXMgZXNjYWxhZGFzOgoKYGBge3J9CmRhdG9zX2VzY2FsYWRvcyA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKHN1YnNldFssMTo1XSkpCnN1YnNldF9lc2MgPC0gY2JpbmQoZGF0b3NfZXNjYWxhZG9zLGNvdmVyX3R5cGUpCnN1YnNldF9lc2MkY292ZXJfdHlwZSA8LSBhcy5mYWN0b3Ioc3Vic2V0X2VzYyRjb3Zlcl90eXBlKQpgYGAKCnRlc3QgZGUgU2hhcGlybyBwYXJhIGxvcyBkYXRvcyBlc2NhbGFkb3M6CgpgYGB7cn0Kc3Vic2V0X2VzYyRjb3Zlcl90eXBlIDwtIGFzLmZhY3RvcihzdWJzZXRfZXNjJGNvdmVyX3R5cGUpCmBgYAoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PScxJywxOjVdKSkKc2hhcGl0ZXN0IDwtIHRlc3QkcC52YWx1ZQp0ZXN0IDwtIG1zaGFwaXJvLnRlc3QodChzdWJzZXRfZXNjW3N1YnNldF9lc2MkY292ZXJfdHlwZT09JzInLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0JHAudmFsdWUpCnRlc3QgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnNldF9lc2Nbc3Vic2V0X2VzYyRjb3Zlcl90eXBlPT0nMycsMTo1XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PSc0JywxOjVdKSkKc2hhcGl0ZXN0IDwtIGFwcGVuZChzaGFwaXRlc3QsdGVzdCRwLnZhbHVlKQp0ZXN0IDwtIG1zaGFwaXJvLnRlc3QodChzdWJzZXRfZXNjW3N1YnNldF9lc2MkY292ZXJfdHlwZT09JzUnLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0JHAudmFsdWUpCnRlc3QgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnNldF9lc2Nbc3Vic2V0X2VzYyRjb3Zlcl90eXBlPT0nNicsMTo1XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PSc3JywxOjVdKSkKc2hhcGl0ZXN0IDwtIGFwcGVuZChzaGFwaXRlc3QsdGVzdCRwLnZhbHVlKQoKc2hhcGl0ZXN0CmBgYAoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhX2VzYyA8LSBsZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfi4sIGRhdGEgPSBzdWJzZXRfZXNjKQoKYGBgCgpgYGB7cn0KdGVzdF9lc2MgPC0gYXMuZGF0YS5mcmFtZShzY2FsZSh0ZXN0KSkKdGVzdF9lc2MgPC0gY2JpbmQodGVzdF9lc2MsY2xhc2UpCnRlc3RfZXNjJGNsYXNlIDwtIGFzLmZhY3Rvcih0ZXN0X2VzYyRjbGFzZSkKYGBgCgpgYGB7cn0KcHJlZGljY2lvbmVzX2VzYyA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fbGRhX2VzYywgbmV3ZGF0YSA9IHRlc3RfZXNjLCBtZXRob2QgPSAicHJlZGljdGl2ZSIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfZXNjJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yX2VzYyA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lc19lc2MkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl9lc2MKYGBgCkVsIG1lam9yIHJlc3VsdGFkbyBmdWUgZWwgaW5pY2lhbC4gRW4gZ2VuZXJhbCBzZSBwcmVkaWNlbiBtdXkgbWFsIGxhcyBjbGFzZXMgbWVub3MgYWJ1bmRhbnRlcy4gCgpFbCBxZGEgY29uIGxvcyBkYXRvcyBlc2NhbGFkb3M6CgpgYGB7cn0KCmxpYnJhcnkoTUFTUykKCm1vZGVsb19xZGFfZXNjIDwtIHFkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+IC4sIGRhdGEgPSBzdWJzZXRbLDE6NSw3XSkKCmBgYAoKYGBge3J9CnByZWRpY2Npb25lc19xZGFfZXNjIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19xZGFfZXNjLCBuZXdkYXRhID0gdGVzdGVvWywxOjUsN10sIG1ldGhvZCA9ICJwcmVkaWN0aXZlIikKdGFibGUoY2xhc2UsIHByZWRpY2Npb25lc19xZGFfZXNjJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9ycWRhX2VzYyA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lc19xZGFfZXNjJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JxZGFfZXNjCmBgYAoKU2UgcmVhbGl6YXJhIGVsIGFuYWxpc2lzIGNvbiBzb2xvIGRvcyBjbGFzZXM6CgoKYGBge3J9CnN1YnN1YnNldDIgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0yKSxdCnN1YnN1YnNldDEgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0xKSxdCnN1YnN1YnNldCA8LXJiaW5kKHN1YnN1YnNldDEsc3Vic3Vic2V0MikKc3Vic3Vic2V0JGNvdmVyX3R5cGUgPC0gZHJvcGxldmVscyhzdWJzdWJzZXQkY292ZXJfdHlwZSkKYGBgCgpFbCB0ZXN0IGRlIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhOgoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKCnRlc3RpbmcgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnN1YnNldFtzdWJzdWJzZXQkY292ZXJfdHlwZT09JzEnLDE6Nl0pKQpzaGFwaXRlc3QgPC0gdGVzdGluZyRwLnZhbHVlCnRlc3RpbmcgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnN1YnNldFtzdWJzdWJzZXQkY292ZXJfdHlwZT09JzInLDE6Nl0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0aW5nJHAudmFsdWUpCgpzaGFwaXRlc3QKYGBgClJlY2hhemEgbm9ybWFsaWRhZCBwYXJhIGNhZGEgdmFyaWFibGUgcGFyYSBjYWRhIG5pdmVsIGRlIGxhIHZhcmlhYmxlLgoKQ29uIGxhcyB2YXJpYWJsZXMgZXN0YW5kYXJpemFkYXM6CgpgYGB7cn0KbGlicmFyeShjbHVzdGVyU2ltKQoKI3N1YnN1YnNldF9lcyA8LSBzY2FsZShzdWJzdWJzZXRbLDE6Nl0sIGNlbnRlciA9IG1lZGlhbihzdWJzdWJzZXQpKQpzdWJzdWJzZXRfZXMgPC0gZGF0YS5Ob3JtYWxpemF0aW9uIChzdWJzdWJzZXRbLDE6Nl0sdHlwZT0ibjIiLG5vcm1hbGl6YXRpb249ImNvbHVtbiIpCnN1YnN1YnNldF9lcyA8LSBjYmluZChzdWJzdWJzZXRfZXMsc3Vic3Vic2V0Wyw3XSkKc3Vic3Vic2V0X2VzIDwtIHJlbmFtZShzdWJzdWJzZXRfZXMsIGNvdmVyX3R5cGU9YHN1YnN1YnNldFssIDddYCkKYGBgCgoKQ29uIGxhcyB2YXJpYWJsZXMgZXN0YW5kYXJpemFkYXMgeSBzYWNhbmRvIGhpbGxzaGFkZToKCmBgYHtyfQpsaWJyYXJ5KG12bm9ybXRlc3QpCgpzdWJzdWJzZXRfMiA8LSBzdWJzdWJzZXRfZXNbLGMoMTo1LDcpXQp0ZXN0aW5nIDwtIG1zaGFwaXJvLnRlc3QodChzdWJzdWJzZXRfMltzdWJzdWJzZXRfMiRjb3Zlcl90eXBlPT0nMScsMTo1XSkpCnNoYXBpdGVzdCA8LSB0ZXN0aW5nJHAudmFsdWUKdGVzdGluZyA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0XzJbc3Vic3Vic2V0XzIkY292ZXJfdHlwZT09JzInLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0aW5nJHAudmFsdWUpCgpzaGFwaXRlc3QKYGBgClJlY2hhemEsIHNpbiBzYWNhciB5IHNhY2FuZG8gaGlsbHNoYWRlLgoKVGVzdGVhbmRvIGhvbW9jZWRhc3RpY2lkYWQ6CgpgYGB7cn0KbGlicmFyeShiaW90b29scykKCmJveE0oZGF0YT1zdWJzdWJzZXRbLDE6Nl0sZ3JvdXBpbmc9c3Vic3Vic2V0Wyw3XSkKYGBgClJlY2hhemEgaWd1YWxkYWQgZGUgdmFyaWFuemFzLgoKQ29tbyBlbCB0ZXN0IE0gZGUgQm94IGVzIHNlbnNpYmxlIGEgbGEgZmFsdGEgZGUgbm9ybWFsaWRhZCwgcmVhbGl6YW1vcyBlbCBkZSBMZXZlbmU6CgpgYGB7cn0KbGlicmFyeShjYXIpCgpsZXZlbmVUZXN0KCBFbGV2YXRpb24gKyBBc3BlY3QgKyBTbG9wZSArIEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5ICsgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cyArIEhpbGxzaGFkZV9Ob29uIH4gc3Vic3Vic2V0JGNvdmVyX3R5cGUsIGRhdGEgPSBzdWJzdWJzZXQpCmBgYAoKRWwgdmFsb3IgZXMgbWVub3IgcXVlIGVsIG5pdmVsIGRlIHNpZ25pZmljYW5jaWEgMC4wMDEuIFJlY2hhemFtb3MgbGEgaGlwb3Rlc2lzIG51bGEgeSBjb25jbHVpbW9zIHF1ZSBsYXMgdmFyaWFuemFzIG5vIHNvbiBpZ3VhbGVzLgoKRGUgdG9kYXMgbWFuZXJhcyBoYWNlbW9zIGVsIHRlc3QgZGUgSG90ZWxsaW5nIHBhcmEgdGVzdGVhciBkaWZlcmVuY2lhIGRlIG1lZGlhczoKCmBgYHtyfQpsaWJyYXJ5KEhvdGVsbGluZykKCmZpdFByb2QgPSBob3RlbGxpbmcudGVzdCgufiBzdWJzdWJzZXQkY292ZXJfdHlwZSwgZGF0YSA9IHN1YnN1YnNldCkgCmZpdFByb2QKYGBgClJlY2hhemEgaWd1YWxkYWQgZGUgbWVkaWFzLCBwZXJvIGNvbW8gbm8gc2UgY3VtcGxlIGxhIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhIGVzdGUgcmVzdWx0YWRvIG5vIGVzIGNvbmZpYWJsZS4KCiMgTERBIGNvbiBkb3MgY2xhc2VzCgplbCBjb25qdW50byBkZSB0ZXN0IHBhcmEgZG9zIGNsYXNlczoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKdGVzdGVvMiA8LXRlc3Rlb1sodGVzdGVvJGNsYXNlPT0yKSxdCnRlc3RlbzEgPC10ZXN0ZW9bKHRlc3RlbyRjbGFzZT09MSksXQp0ZXN0ZW9fMmNsYXNzIDwtcmJpbmQodGVzdGVvMSx0ZXN0ZW8yKQp0ZXN0ZW9fMmNsYXNzJGNsYXNlIDwtIGRyb3BsZXZlbHModGVzdGVvXzJjbGFzcyRjbGFzZSkKY2xhc2VfMmNsYXNzIDwtIHRlc3Rlb18yY2xhc3NbLDddCnRlc3RfMmNsYXNzIDwtdGVzdGVvXzJjbGFzc1ssMTo2XQoKYGBgCgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhXzJjbGFzcyA8LSBsZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfi4gLCBkYXRhID0gc3Vic3Vic2V0KQoKYGBgCgpsYSBjbGFzaWZpY2FjaW9uIGluZ2VudWE6CgpgYGB7cn0KcHJlZGljY2lvbmVzXzJjbGFzc18wIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzLCBuZXdkYXRhID0gc3Vic3Vic2V0KQp0YWJsZShzdWJzdWJzZXQkY292ZXJfdHlwZSwgcHJlZGljY2lvbmVzXzJjbGFzc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYApgYGB7cn0KdHJhaW5pbmdfZXJyb3JfMmNsXzAgPC0gbWVhbihzdWJzdWJzZXQkY292ZXJfdHlwZSAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzXzAkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl8yY2xfMApgYGAKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzLCBuZXdkYXRhID0gdGVzdGVvXzJjbGFzcykKdGFibGUoY2xhc2VfMmNsYXNzLCBwcmVkaWNjaW9uZXNfMmNsYXNzJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbCA8LSBtZWFuKGNsYXNlXzJjbGFzcyAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsCmBgYAoKYGBge3J9CmxpYnJhcnkoa2xhUikgCgpwYXJ0aW1hdChzdWJzdWJzZXQkY292ZXJfdHlwZSB+IC4sIGRhdGEgPSBzdWJzdWJzZXQsIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLCBwbG90Lm1hdHJpeD1GQUxTRSxpbWFnZS5jb2xvcnMgPXJhaW5ib3coMikpCmBgYApMYSB2YXJpYWJsZSBxdWUgbWVqb3Igc2VwYXJhIGVzIGVsZXZhY2lvbi4KCgpDb24gbGFzIHZhcmlhYmxlcyBlc3RhbmRhcml6YWRhczoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX2xkYV8yY2xhc3NfZXN0IDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+LiAsIGRhdGEgPSBzdWJzdWJzZXRfZXMpCgpgYGAKCmxhIGNsYXNpZmljYWNpb24gaW5nZW51YToKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzX2VzXzAgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX2xkYV8yY2xhc3NfZXN0LCBuZXdkYXRhID0gc3Vic3Vic2V0X2VzKQp0YWJsZShzdWJzdWJzZXRfZXMkY292ZXJfdHlwZSwgcHJlZGljY2lvbmVzXzJjbGFzc19lc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbF9lc18wIDwtIG1lYW4oc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzXzJjbGFzc19lc18wJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsX2VzXzAKCmBgYApgYGB7cn0KbGlicmFyeShjbHVzdGVyU2ltKQoKI3N1YnN1YnNldF9lcyA8LSBzY2FsZShzdWJzdWJzZXRbLDE6Nl0sIGNlbnRlciA9IG1lZGlhbihzdWJzdWJzZXQpKQp0ZXN0ZW9fZXMgPC0gZGF0YS5Ob3JtYWxpemF0aW9uICh0ZXN0ZW9fMmNsYXNzWywxOjZdLHR5cGU9Im4yIixub3JtYWxpemF0aW9uPSJjb2x1bW4iKQp0ZXN0ZW9fZXMgPC0gY2JpbmQodGVzdGVvX2VzLHRlc3Rlb18yY2xhc3MkY2xhc2UpCnRlc3Rlb19lcyA8LSByZW5hbWUodGVzdGVvX2VzLCBjbGFzZT1gdGVzdGVvXzJjbGFzcyRjbGFzZWApCmBgYAoKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzX2VzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzX2VzdCwgbmV3ZGF0YSA9IHRlc3Rlb19lcykKdGFibGUodGVzdGVvX2VzJGNsYXNlLCBwcmVkaWNjaW9uZXNfMmNsYXNzX2VzJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbF9lcyA8LSBtZWFuKHRlc3Rlb19lcyRjbGFzZSAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzX2VzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsX2VzCgpgYGAKSGFjaWVuZG8gZWwgZGlzY3JpbWluYW50ZSByb2J1c3RvOgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fcWRhXzJjbGFzc19lc3QgPC0gcWRhKGZvcm11bGEgPSBjb3Zlcl90eXBlIH4uICwgZGF0YSA9IHN1YnN1YnNldF9lcykKCmBgYAoKYGBge3J9CnByZWRpY2Npb25lc3FkYV8yY19lc18wIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19xZGFfMmNsYXNzX2VzdCwgbmV3ZGF0YSA9IHN1YnN1YnNldF9lcykKdGFibGUoc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc3FkYV8yY19lc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9ycWRhXzJjbF9lc18wIDwtIG1lYW4oc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzcWRhXzJjX2VzXzAkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcnFkYV8yY2xfZXNfMAoKYGBgCmBgYHtyfQpwcmVkaWNjaW9uZXNxZGFfMmNfZXMgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX3FkYV8yY2xhc3NfZXN0LCBuZXdkYXRhID0gdGVzdGVvX2VzKQp0YWJsZSh0ZXN0ZW9fZXMkY2xhc2UsIHByZWRpY2Npb25lc3FkYV8yY19lcyRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcnFkYV8yY2xfZXMgPC0gbWVhbih0ZXN0ZW9fZXMkY2xhc2UgIT0gcHJlZGljY2lvbmVzcWRhXzJjX2VzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JxZGFfMmNsX2VzCgpgYGAKSW5jbHVzbyBlbXBlb3JhIHVuIHBvY28uCgojIyBTdXBwb3J0IFZlY3RvciBtYWNoaW5lCgpDbGFzaWZpY2FuZG8gY29uIHN2bSBlbCBkYXRhc2V0IGNvbiBsYXMgNyBjbGFzZXM6CgpgYGB7cn0KbGlicmFyeShlMTA3MSkKCm1vZGVsLnN2bSA9IHN2bSggc3Vic2V0JGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic2V0WyxjKDE6NSw3KV0sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMTAsIHNjYWxlID0gVFJVRSkKcHJpbnQobW9kZWwuc3ZtKQoKYGBgCmBgYHtyfQpwcmVkaWNjaW9uZXNfc3ZtID0gcHJlZGljdChtb2RlbC5zdm0sIHN1YnNldFssYygxOjUsNyldKQoKdGFibGUoc3Vic2V0JGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc19zdm0sIGRubiA9IGMoIkNsYXNlIHJlYWwiLCAiQ2xhc2UgcHJlZGljaGEiKSkKYGBgCmBgYHtyfQp0cmFpbmluZ19lcnJvcl9zdm1fMCA8LSBtZWFuKHN1YnNldCRjb3Zlcl90eXBlICE9IHByZWRpY2Npb25lc19zdm0pICogMTAwIAp0cmFpbmluZ19lcnJvcl9zdm1fMApgYGAKRWwga2VybmVsIHF1ZSBtZWpvciBkYSBlcyBlbCByYWRpYWwuIAoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fdGUgPSBwcmVkaWN0KG1vZGVsLnN2bSwgdGVzdGVvKQoKdGFibGUodGVzdGVvJGNsYXNlLCBwcmVkaWNjaW9uZXNfc3ZtX3RlLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yX3N2bSA8LSBtZWFuKHRlc3RlbyRjbGFzZSAhPSBwcmVkaWNjaW9uZXNfc3ZtX3RlKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3Jfc3ZtCmBgYAoKU2kgbG8gaGFjZW1vcyBjb24gbWVub3MgdmFyaWFibGVzOgoKYGBge3J9CnN1YnN1YnNldDEgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0xKSxdCnN1YnN1YnNldDIgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0yKSxdCnN1YnN1YnNldCA8LXJiaW5kKHN1YnN1YnNldDEsc3Vic3Vic2V0MikKc3Vic3Vic2V0JGNvdmVyX3R5cGUgPC0gZHJvcGxldmVscyhzdWJzdWJzZXQkY292ZXJfdHlwZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkoZTEwNzEpCgptb2RlbC5zdm1fMiA9IHN2bSggc3Vic3Vic2V0JGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic3Vic2V0WyxjKDE6NSw3KV0sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMTAsIHNjYWxlID0gVFJVRSkKcHJpbnQobW9kZWwuc3ZtKQoKYGBgCgoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fMiA9IHByZWRpY3QobW9kZWwuc3ZtXzIsIHN1YnN1YnNldFssYygxOjUsNyldKQoKdGFibGUoc3Vic3Vic2V0JGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc19zdm1fMiwgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKYGBge3J9CnRyYWluaW5nX2Vycm9yX3N2bV8yIDwtIG1lYW4oc3Vic3Vic2V0JGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzX3N2bV8yKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3Jfc3ZtXzIKYGBgCkFob3JhIGNvbiBlbCB0ZXN0OgoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fdGVfMiA9IHByZWRpY3QobW9kZWwuc3ZtXzIsIHRlc3Rlb18yY2xhc3MpCgp0YWJsZSh0ZXN0ZW9fMmNsYXNzJGNsYXNlLCBwcmVkaWNjaW9uZXNfc3ZtX3RlXzIsIGRubiA9IGMoIkNsYXNlIHJlYWwiLCAiQ2xhc2UgcHJlZGljaGEiKSkKYGBgCgpgYGB7cn0KdHJhaW5pbmdfZXJyb3Jfc3ZtXzJfdGUgPC0gbWVhbih0ZXN0ZW9fMmNsYXNzJGNsYXNlICE9IHByZWRpY2Npb25lc19zdm1fdGVfMikgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3N2bV8yX3RlCmBgYApHcmFmaWNhbmRvIHBhcmEgdG9kYXMgbGFzIGNsYXNlczoKCmBgYHtyfQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSxFbGV2YXRpb24gfiBBc3BlY3QsICBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSwgRWxldmF0aW9uIH4gU2xvcGUsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBFbGV2YXRpb24gfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIEVsZXZhdGlvbiB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBBc3BlY3QgfiBTbG9wZSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKYGBgCgpgYGB7cn0KcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIEFzcGVjdCB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSwgQXNwZWN0IH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBTbG9wZSB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCgpgYGAKYGBge3J9CnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sRWxldmF0aW9uIH4gQXNwZWN0LCAgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgRWxldmF0aW9uIH4gU2xvcGUsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIEVsZXZhdGlvbiB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bV8yLCBkYXRhPXN1YnN1YnNldFssYygxOjUsNyldLCBFbGV2YXRpb24gfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bV8yLCBkYXRhPXN1YnN1YnNldFssYygxOjUsNyldLCBBc3BlY3QgfiBTbG9wZSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKYGBgCgpgYGB7cn0KcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgQXNwZWN0IH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIEFzcGVjdCB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3kgfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQoKYGBgCgoKU2UgZGlidWphbiBtYXMgZnJvbnRlcmFzIGN1YW5kbyBsYSB2YXJpYWJsZSBFbGV2YXRpb24gZXN0YSBwcmVzZW50ZS4KCiMjIENsYXNpZmljYWNpb24gbm8gc3VwZXJ2aXNhZGEKClBhcmEgbGEgY2xhc2lmaWNhY2lvbiBubyBzdXBlcnZpc2FkYSwgY29tZW56YW1vcyBjb24gbWV0b2RvcyBqZXJhcnF1aWNvcyBwb3JxdWUgay1tZWFucyBlcyBzZW5zaWJsZSBhIG91dGxpZXJzOgoKYGBge3J9CmRhdG9zX2NsdXN0IDwtIHN1YnNldAptYXRfZGlzdCA8LSBkaXN0KHggPSBzdWJzZXQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSAKCmBgYAoKRGVuZHJvZ3JhbWFzIHBhcmEgZGlzdGludG9zIG1ldG9kb3MsIHN1cG9uZW1vcyA3IGNsdXN0ZXJzOgoKYGBge3J9CmhjX2NvbXBsZXRlIDwtIGhjbHVzdChkID0gbWF0X2Rpc3QsIG1ldGhvZCA9ICJjb21wbGV0ZSIpIApoY19hdmVyYWdlICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAiYXZlcmFnZSIpCmhjX3NpbmdsZSAgIDwtIGhjbHVzdChkID0gbWF0X2Rpc3QsIG1ldGhvZCA9ICJzaW5nbGUiKQpoY193YXJkICAgICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAid2FyZC5EMiIpCmhjX2NuICAgICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAiY2VudHJvaWQiKQpgYGAKCkNvbnN0cnV5ZW5kbyBsb3MgZGVuZHJvZ3JhbWFzOgoKYGBge3J9CmNhbnRpZGFkX2NsdXN0ZXJzID0gNwoKcGxvdChoY19jb21wbGV0ZSkKcmVjdC5oY2x1c3QoaGNfY29tcGxldGUsIGs9Y2FudGlkYWRfY2x1c3RlcnMsIGJvcmRlcj0icmVkIikgIwoKamVyX2NvbXBsZXRlPC1jdXRyZWUoaGNfY29tcGxldGUsaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKZGF0b3NfY2x1c3QkamVyX2NvbXBsZXRlPWplcl9jb21wbGV0ZQoKYGBgCmBgYHtyfQpjYW50aWRhZF9jbHVzdGVycyA9IDcKCnBsb3QoaGNfYXZlcmFnZSkKcmVjdC5oY2x1c3QoaGNfYXZlcmFnZSwgaz1jYW50aWRhZF9jbHVzdGVycywgYm9yZGVyPSJyZWQiKSAjCgpqZXJfYXZlcmFnZTwtY3V0cmVlKGhjX2F2ZXJhZ2Usaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKZGF0b3NfY2x1c3QkamVyX2F2ZXJhZ2U9amVyX2F2ZXJhZ2UKYGBgCgpgYGB7cn0KY2FudGlkYWRfY2x1c3RlcnMgPSA3CgpwbG90KGhjX3NpbmdsZSkKcmVjdC5oY2x1c3QoaGNfc2luZ2xlLCBrPWNhbnRpZGFkX2NsdXN0ZXJzLCBib3JkZXI9InJlZCIpICMKCmplcl9zaW5nbGU8LWN1dHJlZShoY19zaW5nbGUsaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKI2RhdG9zJGplcl9jb21wbGV0ZT1qZXJfY29tcGxldGUKYGBgCgpEYSBmZWlzaW1vLi4uCgpgYGB7cn0KY2FudGlkYWRfY2x1c3RlcnMgPSA3CgpwbG90KGhjX3dhcmQpCnJlY3QuaGNsdXN0KGhjX3dhcmQsIGs9Y2FudGlkYWRfY2x1c3RlcnMsIGJvcmRlcj0icmVkIikgIwoKamVyX3dhcmQ8LWN1dHJlZShoY193YXJkLGs9Y2FudGlkYWRfY2x1c3RlcnMpICAgICAgICAgICAjCmRhdG9zX2NsdXN0JGplcl93YXJkPWplcl93YXJkCmBgYAoKCmBgYHtyfQpjYW50aWRhZF9jbHVzdGVycyA9IDcKCnBsb3QoaGNfY24pCnJlY3QuaGNsdXN0KGhjX2NuLCBrPWNhbnRpZGFkX2NsdXN0ZXJzLCBib3JkZXI9InJlZCIpICMKCmplcl9jbjwtY3V0cmVlKGhjX2NuLGs9Y2FudGlkYWRfY2x1c3RlcnMpICAgICAgICAgICAjCmRhdG9zX2NsdXN0JGplcl9jbj1qZXJfY24KYGBgCgoKTG9zIGNvZWZpY2llbnRlcyBkZSBjb3JyZWxhY2lvbiBjb2ZlbmV0aWNhOgoKYGBge3J9CmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfY29tcGxldGUpKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX2F2ZXJhZ2UpKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX3NpbmdsZSkpCmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfd2FyZCkpCmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfY24pKQpgYGAKRWwgbGlua2FnZSBzaW5nbGUgZXMgZWwgcGVvci4KCmBgYHtyfQpkYXRvc19jbHVzdCRqZXJfY29tcGxldGUgPC0gYXMuZmFjdG9yKGRhdG9zX2NsdXN0JGplcl9jbikKcDEgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9QXNwZWN0LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAyIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PVNsb3BlLCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAyICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAzIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAzICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnA0IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcDQgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQpgYGAKCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGVhc3lHZ3Bsb3QyKQoKZGF0b3NfY2x1c3QkamVyX2NvbXBsZXRlIDwtIGFzLmZhY3RvcihkYXRvc19jbHVzdCRqZXJfY24pCgpwMiA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1TbG9wZSwgY29sb3I9amVyX2NuLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwMyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArbGFicyh5PSdIX0h5ZHJvbG9neScpCgoKcDQgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDcgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Bc3BlY3QsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDkgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1TbG9wZSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY24sYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeT0nSF9Sb2Fkd2F5cycpCgpwMTAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY24sYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeD0nSF9IeWRyb2xvZ3knLHk9J0hfUm9hZHdheXMnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDIscDMscDQscDcscDkscDEwLGNvbHM9MykKCnBuZygnamVyYXJxdWljYWwucG5nJykKCmdncGxvdDIubXVsdGlwbG90KHAyLHAzLHA0LHA3LHA5LHAxMCxjb2xzPTMpCgpkZXYub2ZmKCkKYGBgCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQoKcCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1TbG9wZSwgY29sb3I9amVyX2NuKSkgKwogIGdlb21fcG9pbnQoKQpwICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Bc3BlY3QsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcCArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpCgpwIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9QXNwZWN0LCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcCArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpCgoKcCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PVNsb3BlLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQoKYGBgCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCgpwIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9U2xvcGUsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuKSkgKwogIGdlb21fcG9pbnQoKQpwICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY29tcGxldGUpKSArCiAgZ2VvbV9wb2ludCgpCnAgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQoKCmBgYApTb2xvIGxvIGhpY2UgY29uIGVsIHByaW1lciBtZXRvZG8gZGUgY2x1c3RlciBqZXJhcnF1aWNvIHF1ZSBmdWUgZWwgcXVlIG1lam9yIGRpbyBlbCBjb2VmaWNpZW50ZSBjb2ZyZW5ldGljby4KCgoKCmBgYHtyIGVjaG89VFJVRX0KbGlicmFyeShjbHVzdGVyKQoKZGF0b3Nfa21lYW5zID0gZGF0b3NfY2x1c3RbMTo1XQoKY2FudGlkYWRfY2x1c3RlcnM9NwoKQ0wgID0ga21lYW5zKHNjYWxlKGRhdG9zX2ttZWFucyksY2FudGlkYWRfY2x1c3RlcnMpCmRhdG9zX2ttZWFucyRrbWVhbnMgPSBDTCRjbHVzdGVyCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShlYXN5R2dwbG90MikKCmRhdG9zX2ttZWFucyRrbWVhbnMgPC0gYXMuZmFjdG9yKGRhdG9zX2ttZWFucyRrbWVhbnMpCgpwMSA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Bc3BlY3QsIGNvbG9yPWRhdG9zX2ttZWFucyRrbWVhbnMsIGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnAyIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PVNsb3BlLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwMyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgK2xhYnMoeT0nSF9IeWRyb2xvZ3knKQoKcDQgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnA1IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9QXNwZWN0LCB5PVNsb3BlLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwNiA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucywgYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArbGFicyh5PSdIX0h5ZHJvbG9neScpCgpwNyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDggPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1TbG9wZSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnA5IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9U2xvcGUsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnAxMCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWRhdG9zX2ttZWFucyRrbWVhbnMsYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeD0nSF9IeWRyb2xvZ3knLHk9J0hfUm9hZHdheXMnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDEscDIscDMscDQscDUscDYscDcscDgscDkscDEwLGNvbHM9MykKCnBuZygnamVyYXJxdWljYWxfMi5wbmcnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDEscDIscDMscDQscDUscDYscDcscDgscDkscDEwLGNvbHM9MykKCmRldi5vZmYoKQpgYGAKCgo=